#include "nodeeditor.h"

bool NodeEditor::editNode(SceneNode & node, const SceneNode & root)
{
	entryMap.clear();
	dropdownMap.clear();
	curnode = node;
	
	// backup the current field values
	SceneNode::fields_type backup = node.getFields();
	std::string backupName = node.getName();
	
	// create a dialog and add OK and CANCEL buttons
	Gtk::Dialog dialog;
	dialog.add_button(Gtk::Stock::OK, Gtk::RESPONSE_OK);
	dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
	Gtk::Adjustment scrollAdjustment(0,0,1,0.1,0.9,400);
	Gtk::Adjustment scrollAdjustmenthoriz(0,0,1,0.1,0.9,400);
	Gtk::ScrolledWindow dialog_scrolled_area(scrollAdjustmenthoriz, scrollAdjustment);
	Gtk::VBox dialog_vbox;
	dialog_scrolled_area.set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
	dialog_scrolled_area.add(dialog_vbox);
	//dialog.get_vbox()->pack_start(dialog_scrolled_area, Gtk::PACK_EXPAND_WIDGET, 0);
	dialog.get_vbox()->pack_start(dialog_scrolled_area, true, true, 0);
	//dialog_scrolled_area.get_vadjustment()->set_page_size(400);
	//dialog_scrolled_area.get_vadjustment()->changed();
	/*std::cout << dialog_scrolled_area.get_vadjustment()->get_value() << "," <<
		dialog_scrolled_area.get_vadjustment()->get_lower() << "," <<
		dialog_scrolled_area.get_vadjustment()->get_upper() << "," <<
		dialog_scrolled_area.get_vadjustment()->get_step_increment() << "," <<
		dialog_scrolled_area.get_vadjustment()->get_page_increment() << "," <<
		dialog_scrolled_area.get_vadjustment()->get_page_size() << std::endl;*/
	
	// generate label and entry widgets for each field
	const int padding = 3;
	{ // special case: "name" field
		const std::string field = "name";
		Gtk::HBox * hbox = newWidget<Gtk::HBox>();
		
		Gtk::Label * label = newWidget<Gtk::Label>();
		label->set_text(field);
		hbox->pack_start(*label, Gtk::PACK_EXPAND_WIDGET, padding);
		
		Gtk::Entry * entry = newWidget<Gtk::Entry>();
		entry->set_activates_default(true);
		entry->set_text(node.getName());
		//entry->signal_changed().connect(sigc::mem_fun(*this, &NodeEditor::updateValues));
		hbox->pack_end(*entry, Gtk::PACK_EXPAND_WIDGET, padding);
		entryMap[field] = entry;
		
		dialog_vbox.pack_start(*hbox, Gtk::PACK_EXPAND_WIDGET, padding);
	}
	if (node.getType() == "color")
	{
		colorWheel = newWidget<Gtk::ColorSelection>();
		float r(0),g(0),b(0),a(0);
		node.getField("v00",r);
		node.getField("v01",g);
		node.getField("v02",b);
		node.getField("v03",a);
		Gdk::Color prevColor;
		prevColor.set_red(r*65535.0);
		prevColor.set_green(g*65535.0);
		prevColor.set_blue(b*65535.0);
		colorWheel->set_previous_color(prevColor);
		colorWheel->set_previous_alpha(a*65535.0);
		colorWheel->set_current_color(prevColor);
		colorWheel->set_current_alpha(a*65535.0);
		colorWheel->set_has_opacity_control(true);
		colorWheel->signal_color_changed().connect(sigc::mem_fun(*this, &NodeEditor::updateValues));
		dialog_vbox.pack_start(*colorWheel, Gtk::PACK_EXPAND_WIDGET, padding);
	}
	else
	{
		for (SceneNode::fields_type::iterator i = node.getFields().begin(); i != node.getFields().end(); i++)
		{
			Gtk::HBox * hbox = newWidget<Gtk::HBox>();
			hbox->set_homogeneous(false);
			
			Gtk::Label * label = newWidget<Gtk::Label>();
			label->set_text(i->first);
			//hbox->pack_start(*label, Gtk::PACK_EXPAND_WIDGET, padding);
			hbox->pack_start(*label, Gtk::PACK_SHRINK, padding*4);
			
			// support selecting from a dropdown list of nodes
			bool nodeType = (i->second.getType().substr(0,4) == "node");
			bool selectType = (i->second.getType().substr(0,6) == "select");
			if (nodeType || selectType)
			{
				std::string typeFilter;
				if (i->second.getType().size() > 5)
					typeFilter = i->second.getType().substr(5);
				if (typeFilter.size() < 3)
					typeFilter.clear(); // prob just whitespace
				Gtk::ComboBoxText * dropdown = newWidget<Gtk::ComboBoxText>();
				dropdown->signal_changed().connect(sigc::mem_fun(*this, &NodeEditor::updateValues));
				
				if (nodeType)
				{
					// if our parent is a pass, use it as a type mask
					const SceneNode * parent = root.getParent(node);
					if (parent && parent->getType() != "pass")
						parent = NULL;
					
					// get a list of elligible nodes
					std::vector <const SceneNode *> nodes;
					root.getChildren(nodes, 
									true, 
									typeFilter,
									parent);
					
					std::set <std::string> uniqueNodes;
					for (std::vector <const SceneNode *>::const_iterator n = nodes.begin(); n != nodes.end(); n++)
					{
						uniqueNodes.insert((*n)->getName());
					}
					
					// put the node choices into the dropdown
					for (std::set <std::string>::const_iterator n = uniqueNodes.begin(); n != uniqueNodes.end(); n++)
					{
						dropdown->append_text(*n);
					}
				}
				else if (selectType)
				{
					std::stringstream typeArgs(i->second.getType());
					{
						std::string junk;
						typeArgs >> junk;
					}
					while (typeArgs)
					{
						std::string arg;
						typeArgs >> arg;
						if (!arg.empty())
						{
							if (arg == "unset")
								arg = "";
							dropdown->append_text(arg);
						}
					}
				}
				
				dropdown->set_active_text(i->second.getValue());
				
				hbox->pack_start(*dropdown, Gtk::PACK_EXPAND_WIDGET, padding);
				dropdownMap[i->first] = dropdown;
			}
			else if (i->second.getType() == "bool" || i->second.getType() == "booli")
			{
				Gtk::CheckButton * check = newWidget<Gtk::CheckButton>();
				check->set_active(i->second.getValue() == "1");
				check->signal_toggled().connect(sigc::mem_fun(*this, &NodeEditor::updateValues));
				hbox->pack_start(*check, Gtk::PACK_EXPAND_WIDGET, padding);
				checkMap[i->first] = check;
			}
			else
			{
				Gtk::Entry * entry = newWidget<Gtk::Entry>();
				entry->set_text(i->second.getValue());
				entry->set_activates_default(true);
				entry->signal_changed().connect(sigc::mem_fun(*this, &NodeEditor::updateValues));
				entryMap[i->first] = entry;
				
				if (i->second.getType() == "float")
				{
					/*Gtk::Frame * sliderFrame = newWidget<Gtk::Frame>();
					sliderFrame->set_label("Slider");*/
					
					Gtk::HBox * sliderFrameBox = newWidget<Gtk::HBox>();
					//sliderFrame->add(*sliderFrameBox);
					
					// extract the saved min/max range for the slider
					float savedFrom = i->second.getGuiSliderRange().first;
					float savedTo = i->second.getGuiSliderRange().second;
					
					// extend the range widgets if the current/default value is outside them
					std::string currentstr = i->second.getValue();
					std::stringstream converter(currentstr);
					float currentval;
					converter >> currentval;
					savedFrom = std::min(savedFrom,currentval);
					savedTo = std::max(savedTo,currentval);
					
					Gtk::Entry * fromEntry = newWidget<Gtk::Entry>();
					fromEntry->set_text(tostr(savedFrom));
					fromEntry->set_width_chars(4);
					sliderFrameBox->pack_start(*fromEntry, Gtk::PACK_EXPAND_WIDGET, padding);
					
					Gtk::HScale * slider = newWidget<Gtk::HScale>();
					slider->set_size_request(200,-1);
					sliderFrameBox->pack_start(*slider, Gtk::PACK_EXPAND_WIDGET, padding);
					
					Gtk::Entry * toEntry = newWidget<Gtk::Entry>();
					toEntry->set_text(tostr(savedTo));
					toEntry->set_width_chars(4);
					sliderFrameBox->pack_start(*toEntry, Gtk::PACK_EXPAND_WIDGET, padding);
					
					//hbox->pack_start(*sliderFrame, Gtk::PACK_EXPAND_WIDGET, padding);
					hbox->pack_start(*sliderFrameBox, Gtk::PACK_EXPAND_WIDGET, padding);
					
					fromEntry->signal_changed().connect(sigc::bind(sigc::mem_fun(*this, &NodeEditor::sliderClamp), entry));
					toEntry->signal_changed().connect(sigc::bind(sigc::mem_fun(*this, &NodeEditor::sliderClamp), entry));
					slider->signal_change_value().connect(sigc::bind(sigc::mem_fun(*this, &NodeEditor::sliderMove), entry));
					
					SliderWidgets sliderInfo;
					sliderInfo.fromEntry = fromEntry;
					sliderInfo.toEntry = toEntry;
					sliderInfo.slider = slider;
					sliderInfo.field = &i->second;
					sliderMap[entry] = sliderInfo;
					sliderClamp(entry);
				}
				
				hbox->pack_start(*entry, Gtk::PACK_EXPAND_WIDGET, padding);
				
				// support browsing to select a file for file field types to fill in the entry
				if (i->second.getType() == "file")
				{
					Gtk::Button * button = newWidget<Gtk::Button>();
					button->set_label("Browse");
					button->signal_clicked().connect(
						sigc::bind(
							sigc::mem_fun(*this, &NodeEditor::updateEntryFromBrowse),
							entry)
						);
					hbox->pack_start(*button, Gtk::PACK_EXPAND_WIDGET, padding);
				}
			}
			
			dialog_vbox.pack_start(*hbox, Gtk::PACK_EXPAND_WIDGET, padding);
		}
	}
	
	/*std::cout << dialog_scrolled_area.get_vadjustment()->get_value() << "," <<
		dialog_scrolled_area.get_vadjustment()->get_lower() << "," <<
		dialog_scrolled_area.get_vadjustment()->get_upper() << "," <<
		dialog_scrolled_area.get_vadjustment()->get_step_increment() << "," <<
		dialog_scrolled_area.get_vadjustment()->get_page_increment() << "," <<
		dialog_scrolled_area.get_vadjustment()->get_page_size() << std::endl;*/
	
	//if (dialog_scrolled_area.get_vscrollbar()->get_visible())
	{
		int dw,dh;
		dialog.get_size(dw,dh);
		int bw,bh;
		bh = node.getFields().size()*40;
		dialog.resize(dw,std::max(std::min(800,bh),dh));
	}
	
	dialog.set_default_response(Gtk::RESPONSE_OK);
	dialog.set_response_sensitive(Gtk::RESPONSE_OK);
	dialog.show_all();
	
	// run this once now just to make sure everything is synced up
	updateValues();
	
	int result = dialog.run();
	if (result == Gtk::RESPONSE_OK)
	{
		updateValues();
		return true;
	}
	else
	{
		node.getFields() = backup;
		node.setName(backupName);
		node.updateFloatVars();
		return false;
	}
}

void NodeEditor::updateValues()
{
	for (entryMap_type::const_iterator i = entryMap.begin(); i != entryMap.end(); i++)
	{
		if (i->first == "name")
			curnode->setName(i->second->get_text());
		else
		{
			curnode->getFields().find(i->first)->second.setValue(i->second->get_text());
			
			// use the changed value to reposition the slider
			sliderMap_type::iterator s = sliderMap.find(i->second);
			if (s != sliderMap.end())
			{
				float val(0.0);
				std::stringstream str(i->second->get_text());
				str >> val;
				s->second.slider->set_value(val);
			}
		}
	}
	
	for (dropdownMap_type::const_iterator i = dropdownMap.begin(); i != dropdownMap.end(); i++)
	{
		curnode->getFields().find(i->first)->second.setValue(i->second->get_active_text());
	}
	
	for (checkMap_type::const_iterator i = checkMap.begin(); i != checkMap.end(); i++)
	{
		curnode->getFields().find(i->first)->second.setValue(i->second->get_active() ? "1" : "0");
	}
	
	if (colorWheel)
	{
		curnode->setField("v00", colorWheel->get_current_color().get_red_p());
		curnode->setField("v01", colorWheel->get_current_color().get_green_p());
		curnode->setField("v02", colorWheel->get_current_color().get_blue_p());
		curnode->setField("v03", colorWheel->get_current_alpha()/65535.0);
	}
	
	curnode->updateFloatVars();
}

void NodeEditor::updateEntryFromBrowse(Gtk::Entry * entry)
{
	Gtk::FileChooserDialog dialog("Please choose a file",Gtk::FILE_CHOOSER_ACTION_OPEN);
	//dialog.set_transient_for(mainWindow);
	
	//Add response buttons the the dialog:
	dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
	dialog.add_button(Gtk::Stock::OPEN, Gtk::RESPONSE_OK);
	
	Gtk::FileFilter filter_any;
	filter_any.set_name("All files");
	filter_any.add_pattern("*");
	dialog.add_filter(filter_any);
	
	//Show the dialog and wait for a user response:
	int result = dialog.run();
	
	//Handle the response:
	if (result == Gtk::RESPONSE_OK)
	{
		std::string filename = dialog.get_filename();
		entry->set_text(filename);
	}
}

void NodeEditor::sliderClamp(Gtk::Entry * entry)
{
	sliderMap_type::iterator s = sliderMap.find(entry);
	if (s != sliderMap.end())
	{
		float from(0);
		{
			std::stringstream str(s->second.fromEntry->get_text());
			str >> from;
		}
		float to(0);
		{
			std::stringstream str(s->second.toEntry->get_text());
			str >> to;
		}
		
		// store the clamp values in the node
		s->second.field->setGuiSliderRange(from, to);
		
		from = std::min(to,from);
		s->second.slider->get_adjustment()->set_lower(from);
		s->second.slider->get_adjustment()->set_upper(to);
		float step = (to-from)/100.0;
		s->second.slider->get_adjustment()->set_step_increment(step);
		int digits = 1;
		if (step < 0.1)
			digits = 2;
		if (step < 0.01);
			digits = 3;
		if (step < 0.001)
			digits = 4;
		s->second.slider->set_digits(digits);
	}
}

bool NodeEditor::sliderMove(Gtk::ScrollType scroll, double new_value, Gtk::Entry * entry)
{
	sliderMap_type::iterator s = sliderMap.find(entry);
	if (s != sliderMap.end())
	{
		//clamp new_value because it seems like the slider doesn't
		new_value = std::max(new_value, s->second.slider->get_adjustment()->get_lower());
		new_value = std::min(new_value, s->second.slider->get_adjustment()->get_upper());
		
		std::stringstream str;
		str << new_value;
		s->first->set_text(str.str());
	}
	
	return false;
}
